﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MSAS.FrontEnd.DL;

namespace MRS.FrontEnd.BO
{
    /// <summary>
    /// Class to hold the content of a MRS Object
    /// </summary>
    public class UnpackedObject
    {
        #region Members

        /// <summary>
        /// Gets or sets the object type from the enum 
        /// <see cref="ObjectTypeEnum.cs"/>
        /// </summary>
        public ObjectType Type { get; set; }

        /// <summary>
        /// Gets or sets the SHA256 of the object as a byte[]
        /// </summary>
        public byte[] Sha256 { get; set; }

        /// <summary>
        /// Gets or sets the object version
        /// </summary>
        public long Version { get; set; }

        /// <summary>
        /// Gets or sets the flags of the object (used for testing)
        /// </summary>
        public byte Flags { get; set; }

        /// <summary>
        /// Gets or sets the expiration of the object server side in minutes
        /// </summary>
        public int ServerExpirationInMinutes { get; set; }

        /// <summary>
        /// Gets or sets the expiration of the object client side in minutes
        /// </summary>
        public int ClientExpirationInMinutes { get; set; }

        /// <summary>
        /// Gets or sets the dictionary of Metadata assigned to the object
        /// </summary>
        public Dictionary<string, object> Metadatas { get; set; }

        /// <summary>
        /// Gets or sets the list of the Reputations assigned to this object
        /// </summary>
        public IList<Reputation> Reputations { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Construct a new instance of UnpackObject by creating the dictionary of metadata and a list of reputation
        /// </summary>
        public UnpackedObject()
        {
            Metadatas = new Dictionary<string, object>();
            Reputations = new List<Reputation>();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Create and return the proper codec based on the type of the object
        /// </summary>
        /// <param name="type">first byte of the binary or the type from the UnpackObject.Type</param>
        /// <returns>the instance of the codec to use</returns>
        public static IReputationBinaryCodec ReputationBinaryCodecFactory(ObjectType type)
        {
            switch (type)
            {
                case ObjectType.URL :
                    return UrlBinaryCodec.Instance;

                default :
                    throw new NotImplementedException("ObjectType not supported");
            }
        }

        /// <summary>
        /// Alternate method to get the factory when only a byte is provided
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IReputationBinaryCodec ReputationBinaryCodecFactory(byte type)
        {
            return ReputationBinaryCodecFactory((ObjectType)type);
        }

        /// <summary>
        /// Alternate method to get the factory with passing a byte array
        /// </summary>
        /// <param name="packedObject"></param>
        /// <returns></returns>
        public static IReputationBinaryCodec ReputationBinaryCodecFactory(byte[] packedObject)
        {
            return ReputationBinaryCodecFactory((ObjectType)packedObject[0]);
        }

        /// <summary>
        /// Return a list of overrides since the last execution
        /// </summary>
        /// <param name="lastStamp"></param>
        /// <returns></returns>
        public static List<Override> GetLatestOverride(int? lastStamp)
        {
            // Get the results from the data layer
            List<prc_GetOverridesResult> results = DataRetrieve.GetOverrides(lastStamp);

            List<Override> overrides = new List<Override>();
            foreach (prc_GetOverridesResult result in results)
            {
                // find the corresponding codec
                IReputationBinaryCodec codec = ReputationBinaryCodecFactory(result.ReputationBinary.ToArray());
                overrides.Add(new Override
                {
                    Id = result.OverrideId,
                    Expiration = result.ExpirationDT,
                    unpackedObject = codec.UnpackObjectBinary(result.ReputationBinary.ToArray()),
                    packedObject = result.ReputationBinary.ToArray()
                });
            }
            return overrides;
        }

        /// <summary>
        /// Helper method to retrieve all the objects that are queried.
        /// This method groups the object by type and return the list from the database
        /// </summary>
        /// <param name="objects"></param>
        /// <returns></returns>
        public static List<UnpackedObject> GetUnpackedObjects(IList<ObjectIdentifier> objects)
        {
            List<UnpackedObject> results = new List<UnpackedObject>();

            // get the distinct list of object types requested
            IEnumerable<ObjectType> types = objects.GroupBy(ot => ot.Type).Select(t => t.First().Type);

            foreach (ObjectType type in types)
            {
                IReputationBinaryCodec codec = ReputationBinaryCodecFactory(type);
                IEnumerable<byte[]> sha256s = objects.Where(o => o.Type == type).Select(o => o.Sha256);
                List<byte[]> searchResults = DataRetrieve.LookupObjects((short) type, sha256s.ToList() );
                results.AddRange( searchResults.Select(r => codec.UnpackObjectBinary(r)));
            }
            return results;
        }

        /// <summary>
        /// Helper method to retrieve all the objects that are queried as packed byte[]
        /// </summary>
        /// <param name="objects"></param>
        /// <returns></returns>
        public static List<byte[]> GetPackedObjects(IList<ObjectIdentifier> objects)
        {
            List<byte[]> results = new List<byte[]>();

            // get the distinct list of object types requested
            IEnumerable<ObjectType> types = objects.GroupBy(ot => ot.Type).Select(t => t.First().Type);

            foreach (ObjectType type in types)
            {
                IEnumerable<byte[]> sha256s = objects.Where(o => o.Type == type).Select(o => o.Sha256);
                List<byte[]> searchResults = DataRetrieve.LookupObjects((short)type, sha256s.ToList());
                results.AddRange(searchResults);
            }
            return results;
        }

        #endregion
    }

    /// <summary>
    /// Class to hold a single metadata object
    /// </summary>
    public class Metadata
    {
        #region Members

        /// <summary>
        /// Gets or sets the name of the metadata
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets the value of the metadata, this is an object so the user must check the type of the object
        /// </summary>
        public object Value { get; set; }

        #endregion
    }

    /// <summary>
    /// Class to hold the reputation of an object
    /// </summary>
    public class Reputation
    {
        #region Members

        /// <summary>
        /// Gets or sets the category ID of the reputation from the taxonomy
        /// </summary>
        public int Category { get; set; }

        /// <summary>
        /// Gets or sets the score of the reputation, usually between 0, 50 or 100
        /// </summary>
        public byte Confidence { get; set; }

        /// <summary>
        /// Gets or sets the dictionary of metadata assigned to the reputation
        /// </summary>
        public Dictionary<string, object> Metadatas { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Construct a new instance of the Reputation by defining a new dictionary to assign metadata
        /// </summary>
        public Reputation()
        {
            Metadatas = new Dictionary<string, object>();
        }

        #endregion
    }

    /// <summary>
    /// Class to hold an override returned by the database
    /// </summary>
    public class Override
    {
        #region Members
        /// <summary>
        /// Gets or sets the ID of the override, always increasing
        /// </summary>
        public long Id { get; set; }

        /// <summary>
        /// Indicate the disabling date of the override
        /// </summary>
        public DateTime? Expiration { get; set; }

        /// <summary>
        /// Object of the override
        /// </summary>
        public UnpackedObject unpackedObject { get; set; }

        /// <summary>
        /// Binary representation of the object
        /// </summary>
        public byte[] packedObject { get; set; }

        #endregion
    }

    /// <summary>
    /// Class to hold an object identifier, combination of the type and sha256 of the object
    /// </summary>
    public class ObjectIdentifier
    {
        #region Members
        /// <summary>
        /// Gets or sets the ObjectType of the identifier
        /// </summary>
        public ObjectType Type { get; set; }

        /// <summary>
        /// Gets or sets the Sha256 of the identifier
        /// </summary>
        public byte[] Sha256 { get; set; }

        #endregion
    }
}
